﻿
namespace BLPicker.OnTheFly.Tools
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.Reflection;

    public class ProxySourceCodeBuilder 
    {

        public string SourceCode { get; private set; }

        public IEnumerable<Assembly> ReferencedAssemblies
        {
            get { return _referencedAssemblies; }
        }

        public void Build(Type businessLayerType, string nameSpace, string className)
        {
            Contract.Requires(businessLayerType != null);
            Contract.Requires(businessLayerType.IsInterface);
            Contract.Requires(!string.IsNullOrWhiteSpace(className));

            SourceCode = FormatClass(businessLayerType, nameSpace, className);
        }

        private readonly List<Assembly> _referencedAssemblies = new List<Assembly>();

        private const string ClassTemplate = @"
namespace {0}
{{
    public class {1} : {2}{3}
    {{

        private readonly IBusinessLayerFactory<{2}{3}> _businessLayerFactory;

        public {1}(IBusinessLayerFactory<{2}{3}> businessLayerFactory)
        {{
            _businessLayerFactory = businessLayerFactory;
        }}

{4}

{5}

{6}

    }}
}}
";
        private const string MethodTemplate = @"
        public {0} {1}{2}({3}) {{ {5}_businessLayerFactory.GetBusinessLayer({6}).{1}{2}({4}); }}
";
        private const string PropertyTemplate = @"
        public {0} {1}
        {{
            {2}{3}{4}
        }}
";
        private const string PropertyGetterTemplate = @"get{{ return _businessLayerFactory.GetBusinessLayer().{0}; }}";
        private const string PropertySetterTemplate = @"set{{ _businessLayerFactory.GetBusinessLayer().{0} = value; }} ";
        private const string EventTemplate = @"
        public event {0} {1}
        {{
            add{{ _businessLayerFactory.GetBusinessLayer().{1} += value; }}
            remove{{ _businessLayerFactory.GetBusinessLayer().{1} -= value; }}
        }}
";

        private string FormatClass(Type businessLayerType, string nameSpace, string className)
        {
            Contract.Requires(businessLayerType != null);
            Contract.Requires(businessLayerType.IsInterface);
            Contract.Requires(!string.IsNullOrWhiteSpace(nameSpace));
            Contract.Requires(!string.IsNullOrWhiteSpace(className));

            Type[] genericArguments = businessLayerType.GetGenericArguments();
            IEnumerable<MethodInfo> methodInfos = businessLayerType.GetMethods().Where(mi => !mi.IsSpecialName);
            IEnumerable<PropertyInfo> propertyInfos = businessLayerType.GetProperties();
            IEnumerable<EventInfo> eventInfos = businessLayerType.GetEvents();

            string result = string.Format(
                ClassTemplate,
                nameSpace,
                className,
                string.Format("{0}.{1}", businessLayerType.Namespace, businessLayerType.GetSimpleName()),
                genericArguments.Any() ? string.Format("<{0}>", string.Join(",", genericArguments.Select(ga => ga.FullName))) : string.Empty,
                string.Join("\n", methodInfos.Select(FormatMethod)),
                string.Join("\n", propertyInfos.Select(FormatProperty)),
                string.Join("\n", eventInfos.Select(FormatEvent)));

            LinkAssembly(businessLayerType.Assembly);

            return result;
        }

        private string FormatMethod(MethodInfo methodInfo)
        {
            Contract.Requires(methodInfo != null);

            DependencyMethodAttribute attribute = methodInfo
                .GetCustomAttributes(typeof(DependencyMethodAttribute), true)
                .SingleOrDefault() as DependencyMethodAttribute;

            Type methodReturnType = methodInfo.ReturnType;
            bool isVoid = methodReturnType == typeof(void);
            string methodName = methodInfo.Name;
            string methodDependencyName = attribute != null ? attribute.Name : string.Empty;
            Type[] genericArguments = methodInfo.GetGenericArguments();
            ParameterInfo[] parameters = methodInfo.GetParameters();

            LinkAssembly(methodReturnType.Assembly);
            foreach (ParameterInfo pi in parameters)
                LinkAssembly(pi.ParameterType.Assembly);

            string result = string.Format(
                MethodTemplate,
                isVoid ? "void" : methodReturnType.FullName,
                methodName,
                genericArguments.Any() ? string.Format("<{0}>", string.Join(",", genericArguments.Select(ga => ga.Name))) : string.Empty,
                string.Join(", ", parameters.Select(pi => string.Format("{0} {1}", pi.ParameterType.FullName, pi.Name))),
                string.Join(", ", parameters.Select(pi => pi.Name)),
                isVoid ? string.Empty : "return ",
                !string.IsNullOrWhiteSpace(methodDependencyName) ? string.Format("\"{0}\"", methodDependencyName) : string.Empty);

            return result;
        }

        private string FormatProperty(PropertyInfo propertyInfo)
        {
            Contract.Requires(propertyInfo != null);

            Type propertyType = propertyInfo.PropertyType;
            string propertyName = propertyInfo.Name;
            Type declaringType = propertyInfo.DeclaringType;
            bool hasGetter = propertyInfo.CanRead;
            bool hasSetter = propertyInfo.CanWrite;

            LinkAssembly(propertyType.Assembly);

            string result = string.Format(
                PropertyTemplate,
                propertyType,
                propertyName,
                hasGetter ? string.Format(PropertyGetterTemplate, propertyName) : string.Empty,
                hasGetter && hasSetter ? "\n            " : string.Empty,
                hasSetter ? string.Format(PropertySetterTemplate, propertyName) : string.Empty);

            return result;
        }

        private string FormatEvent(EventInfo eventInfo)
        {
            Contract.Requires(eventInfo != null);

            Type eventHandlerType = eventInfo.EventHandlerType;
            string eventName = eventInfo.Name;

            LinkAssembly(eventHandlerType.Assembly);

            string result = string.Format(
                EventTemplate,
                eventHandlerType.FullName,
                eventInfo.Name);

            return result;
        }

        private void LinkAssembly(Assembly assembly)
        {
            Contract.Requires(assembly != null);

            if (!_referencedAssemblies.Any(a => a.FullName == assembly.FullName))
                _referencedAssemblies.Add(assembly);
        }

    }
}
